package cs

import (
	//"github.com/gogo/protobuf/proto"
	"fmt"
	"github.com/sniperHW/fly/core/buffer"
	"github.com/sniperHW/fly/core/compress"
	"github.com/sniperHW/fly/net/pb"
	_ "github.com/sniperHW/fly/proto"
	"reflect"
)

const (
	SizeSeqNo      = 8
	SizeLen        = 4
	SizeFlag       = 1
	SizeCmd        = 2
	SizePB         = 4
	SizeErrCode    = 2
	SizeTimeout    = 4
	SizeErrDescLen = 2
	SizeUniKeyLen  = 2
	minSize        = SizeLen
	MaxPacketSize  = 8 * 1024 * 1024
)

type Encoder struct {
	compressor compress.CompressorI
	pbSpace    *pb.Namespace
}

func NewEncoder(pbSpace *pb.Namespace, docompress bool) *Encoder {
	e := &Encoder{
		pbSpace: pbSpace,
	}
	if docompress {
		e.compressor = &compress.ZipCompressor{}
	}
	return e
}

func (this *Encoder) EnCode(o interface{}, buff *buffer.Buffer) error {

	m, ok := o.(*Message)

	if !ok {
		if nil == m {
			panic("o is nil")
		}
		return fmt.Errorf("invaild object to encode:%s", reflect.TypeOf(o).String())
	}

	var pbbytes []byte
	var cmd uint32 = uint32(m.cmd)
	var err error
	var payloadLen int
	var totalLen int
	var flag byte
	var sizeOfErrDesc int

	if nil != m.GetData() {

		if pbbytes, cmd, err = this.pbSpace.Marshal(m.GetData()); err != nil {
			return nil
		}

		if this.compressor != nil && len(pbbytes) >= 1024 {
			pbbytes, _ = this.compressor.Compress(pbbytes)
			flag = byte(1)
		}
	}

	head := m.GetHead()

	sizeOfUniKey := len(head.UniKey)

	if sizeOfUniKey > 0xFF {
		return fmt.Errorf("UniKey too large %d", sizeOfUniKey)
	}

	errCode := int16(0)

	if nil != head.Err && head.Err.Code != 0 {
		errCode = head.Err.Code
		sizeOfErrDesc = len(head.Err.Desc)
		if sizeOfErrDesc > 0xFF {
			//描述超长，直接丢弃
			sizeOfErrDesc = SizeErrDescLen
		} else {
			sizeOfErrDesc += SizeErrDescLen
		}
	}

	sizeOfHead := SizeSeqNo + SizeErrCode + sizeOfErrDesc + SizeUniKeyLen + sizeOfUniKey + SizeTimeout

	payloadLen = sizeOfHead + SizeFlag + SizeCmd + SizePB + len(pbbytes)
	totalLen = SizeLen + payloadLen
	if uint64(totalLen) > MaxPacketSize {
		return nil
	}
	//len + flag + cmd + pbbytes
	//写payload大小
	buff.AppendUint32(uint32(payloadLen))
	//写head

	//seqno
	buff.AppendInt64(head.Seqno)

	//err
	buff.AppendInt16(errCode)

	if sizeOfErrDesc > 0 {
		buff.AppendUint16(uint16(sizeOfErrDesc - SizeErrDescLen))
		if sizeOfErrDesc > SizeErrDescLen {
			buff.AppendString(head.Err.Desc)
		}
	}

	//unikey
	buff.AppendInt16(int16(sizeOfUniKey))
	if sizeOfUniKey > 0 {
		buff.AppendString(head.UniKey)
	}

	//timeout
	buff.AppendUint32(head.Timeout)

	//写flag
	buff.AppendByte(flag)
	//写cmd
	buff.AppendUint16(uint16(cmd))

	buff.AppendInt32(int32(len(pbbytes)))

	if len(pbbytes) > 0 {
		//写数据
		buff.AppendBytes(pbbytes)
	}

	return nil
}
